#include "fxcc/core/graph/opengl3/ImGuiGlfwApp.h"
#include "fxcc/core/graph/common/Geometry.h"
#include "fxcc/core/graph/common/BoneNode.h"

#include "fxcc/core/graph/common/Scene.h"
#include "fxcc/core/AssertManager.h"
#include "fxcc/core/graph/common/Resources.h"
#include "fxcc/imgui/Editor.h"

using namespace fxcc::platform;
using namespace fxcc::graph;
using namespace fxcc::graph::opengl3;
using namespace fxcc::imgui::graph::opengl3;
using BaseApp = fxcc::imgui::graph::opengl3::GlfwApp;

#undef main

struct SimpleApp : public BaseApp
{
public:
    fxcc::graph::common::Transform transform;

    int useSoftShadows{ 0 };

    std::shared_ptr<fxcc::graph::opengl3::RenderTargetJack> m_RenderTarget;

    std::shared_ptr<fxcc::graph::opengl3::RenderTargetJack> m_DepthRT;

    std::shared_ptr<fxcc::graph::opengl3::Texture2DJack> m_Texture2DJack;

    glm::vec4 color;

    std::shared_ptr<fxcc::graph::opengl3::Effect3d> m_Effect;

    std::shared_ptr<fxcc::graph::opengl3::Effect3d> m_DepthEffect;

    std::shared_ptr<fxcc::graph::common::BoneNode> m_BoneNode;

    fxcc::graph::common::Camera camera;

    fxcc::graph::common::OrthoCamera lightCamera;

    bool  m_UpdateLight{ true };

    float m_Theta = { 0 };
    
    int halfkernelSize{ 1 };

    SimpleApp(const fxcc::platform::common::App::Desc &desc) : BaseApp(desc), color(1.0)
    {
    }
    bool Init() override
    {
        if (!BaseApp::Init())
        {
            return false;
        }

        if (!InitResources())
        {
            return false;
        }

        return true;
    };

    fxcc::graph::common::Effect3d::BoneData m_BoneData;

    void printOpenGLESVersion()
    {
        const char *version = (const char *)glGetString(GL_VERSION);
        std::cout << "OpenGL ES Version: " << version << std::endl;
    };
    glm::ivec2 depthMapSize{ 2048,2048};


    bool InitResources()
    {
        lightCamera.SetPosition(glm::vec3(300));
        lightCamera.LookAt(glm::vec3(0.f));

        lightCamera.SetHalfExtends(glm::vec3(depthMapSize.x / 2, depthMapSize.y / 2, 0));
        lightCamera.zNear = 0;
        lightCamera.zFar = 1200;


        transform.m_Scale = glm::vec3(1.0f);

        camera.SetPosition(glm::vec3(50.0f));
        camera.LookAt(glm::vec3(0.0f));
        camera.nNear = 1.0f;
        //camera.nFar= 2500.0f;

        const std::string fragCode = R"(

layout (location=0) out vec4 FragColor;

uniform sampler2D diffuse;
uniform sampler2D shadowMap;

uniform vec3 m_LightDir;
uniform mat4 m_LightProjView;
uniform int useSoftShadows;
uniform vec2 texelSize;
uniform int halfkernelSize;


float ShadowCalculation(vec4 fragPosLightSpace)
{
    vec3 projCoords=fragPosLightSpace.xyz/ fragPosLightSpace.w;
    projCoords=projCoords*0.5f+0.5f;
    
    float closestDepth = texture(shadowMap, projCoords.xy).r;
    float currentDepth = projCoords.z;

    float bias=0.005f;
    float shadow= currentDepth-bias>closestDepth ? 1.0f:0.0f;
    return shadow;

}

float CalculatePCFPercentLit( vec2 shadowTexCoord, float compareDepth)
{
    float shadow = 0.0f;
    int kernelSize=halfkernelSize*2+1;

    vec2 offset = texelSize; 

    for (int x = -halfkernelSize; x <= halfkernelSize; ++x)
    {
        for (int y = -halfkernelSize; y <= halfkernelSize; ++y)
        {
            vec2 offsetTexCoord = shadowTexCoord + vec2(x*offset.x, y*offset.y)  ;
            float shadowMapDepth = texture(shadowMap, offsetTexCoord).r; 
            if (compareDepth > shadowMapDepth)
                shadow += 1.0; 
        }
    }

    return (shadow / float(kernelSize * kernelSize)); 
};


void main()
{
  
    vec4 FragPosLightSpace=m_LightProjView *vec4(FragPos,1.0f);
    vec3 TexCoordsLightSpace= FragPosLightSpace.xyz;
    TexCoordsLightSpace=TexCoordsLightSpace*0.5f+0.5f;
    
    float shadow=0.0f;
    
    if(useSoftShadows!=0)
    {
        shadow = CalculatePCFPercentLit(TexCoordsLightSpace.xy,TexCoordsLightSpace.z);
    }
    else
    {
        shadow = ShadowCalculation(FragPosLightSpace);
    }
    vec4 diffuseColor=texture(diffuse , TexCoords);

    vec3 lightColor=vec3(1.0f);
    vec3 diffuse= diffuseColor.xyz*  (1.f- shadow);
    float diff= max(dot(-m_LightDir, Normal),0.0f);

    vec3 lightDir[4] =
    {
        vec3(-1.0f, 1.0f, -1.0f),
        vec3(1.0f, 1.0f, -1.0f),
        vec3(0.0f, -1.0f, 0.0f),
        vec3(1.0f, 1.0f, 1.0f)
    };

    float lighting = 
                 clamp(dot(lightDir[0], Normal), 0.0, 1.0) * 0.05 +
                 clamp(dot(lightDir[1], Normal), 0.0, 1.0) * 0.05 +
                 clamp(dot(lightDir[2], Normal), 0.0, 1.0) * 0.05 +
                 clamp(dot(lightDir[3], Normal), 0.0, 1.0) * 0.05;
    
    float shadowLighting = lighting * 0.5;
    lighting += clamp(dot(- m_LightDir, Normal), 0.0, 1.0);
    lighting = mix(shadowLighting, lighting, 1.0f-shadow);
    
    FragColor= vec4(lighting * diffuse,1.0f);

    // float shadowLighting = lighting * 0.5f;
    // lighting += saturate(dot(-g_LightDir, input.normalW));
    // lighting = lerp(shadowLighting, lighting, percentLit);
    // return lighting * visualizeCascadeColor * diffuse;
    // FragColor=vec4(diffuse*diff,1.0f);

}
)";
m_Effect = std::make_shared<fxcc::graph::opengl3::Effect3d>(fragCode);
        
        const std::string fragCode2 = R"(
void main()
{
}
)";
        m_DepthEffect = std::make_shared<fxcc::graph::opengl3::Effect3d>(fragCode2);
        m_DepthEffect->m_PipeLineType.m_RasterizerType = fxcc::graph::common::RasterizerType::_NONE_;

        
        auto& am = fxcc::AssertManager::Inst();
        {
            fxcc::graph::common::Texture2DJack::Desc desc;
            desc.SetDDS(am.GetFile("Model/Sponza/sponza_fabric_blue_diff.dds"));
            desc.SetWIC(am.GetFile("container.jpg"));
            m_Texture2DJack = std::make_shared<fxcc::graph::opengl3::Texture2DJack>(desc);

        }
        {

            std::string scenePath = fxcc::AssertManager::GetFile("Model/powerplant/powerplant.gltf");
     
            fxcc::Base::normalizePath;

            auto& commonRS = fxcc::graph::common::Resources::Inst();

            if (!commonRS.LoadScene(scenePath))
            {
                ztclog::info("failed load graph rs");
                return false;
            }

            if (commonRS.HasScene(scenePath))
            {
                auto scene = commonRS.GetScene(scenePath);
                m_BoneNode = std::make_shared<fxcc::graph::common::BoneNode>(scene.get());
            }

        }

        {
            fxcc::graph::common::RenderTargetJack::Desc desc(0, depthMapSize.x, depthMapSize.y, 4, 0);
            m_DepthRT = std::make_shared<fxcc::graph::opengl3::RenderTargetJack>(desc);
        }

        return true;
    };

    fxcc::imgui::Editor editor;

    void OnEdit() override
    {
        ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());

        ImGui::Begin("RT0");
        editor.EditInst<fxcc::graph::common::RenderTargetJack>("rt", *m_RenderTarget);
        ImGui::End();

        //ImGui::Begin("texture");
        //editor.EditInst<fxcc::graph::common::Texture2DJack>("diffuse", *m_Texture2DJack);
        //ImGui::End();

        ImGui::Begin("depth stencil");
        editor.AddFrameBuffer(m_RenderTarget->m_DepthStencilTexture->GetTextureId());
        ImGui::End();

        ImGui::Begin("light depth stencil");
        editor.AddFrameBuffer(m_DepthRT->m_DepthStencilTexture->GetTextureId());
        ImGui::End();

        ImGui::Begin("attachment0");
        editor.AddFrameBuffer(m_RenderTarget->m_Textures[0]->GetTextureId());
        ImGui::End();

        ImGui::Begin("commonResources");
        auto& commonRS = fxcc::graph::common::Resources::Inst();

        for (auto [key, value] : commonRS.m_Materials)
        {
            editor.EditInst<fxcc::graph::common::MaterialData>(value->m_Name, *value);
        }
        ImGui::End();

        ImGui::Begin("opengl Resources");
        for (auto [key, value] : commonRS.m_Materials)
        {
            //editor.EditInst<fxcc::graph::common::MaterialData>(value->.m_Name.c_str(), *value);
        }
        ImGui::End();

        ImGui::Begin("shadow");
        editor.CheckBoxInst<int>("useSoftshadow", useSoftShadows);
        editor.CheckBoxInst<bool>("m_UpdateLight", m_UpdateLight);
        editor.EditInst<int>("halfkernelSize", halfkernelSize);

        ImGui::End();
    }
    void OnDestroy() override
    {
      
        BaseApp::OnDestroy();
    }
    void OnFrameRender() override
    {
        auto& commonRS = fxcc::graph::common::Resources::Inst();

        m_RenderTarget->Begin();
        m_Effect->ClearBgColor(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
        m_Effect->ClearBuffer(1, 1);
        m_Effect->Begin();
        m_Effect->SetCamera(camera);
        m_Effect->m_Shader->Use();
        m_Effect->m_Shader->setInt("diffuse", 0);
        
        // Set Light Depth 
        m_Effect->m_Shader->setInt("shadowMap", 1);
        m_Effect->m_Shader->setInt("halfkernelSize", halfkernelSize);
        auto shadowMap = m_DepthRT->m_DepthStencilTexture;
        shadowMap->Active(1);
        
        glm::vec2 shadowMapSize;
        shadowMapSize.x = shadowMap->m_Desc.m_MipmapData.m_Width;
        shadowMapSize.y = shadowMap->m_Desc.m_MipmapData.m_Height;

        glm::vec2 texelSize = glm::vec2(1.f / shadowMapSize.x, 1.f / shadowMapSize.y);
        m_Effect->m_Shader->setVec2("texelSize", texelSize);
        m_Effect->m_Shader->setInt("useSoftShadows", useSoftShadows);

        m_Effect->m_Shader->setVec3("m_LightDir", lightCamera.GetForward());
        m_Effect->m_Shader->setMat4("m_LightProjView", lightCamera.GetProjViewMatrix());

        for (auto [key, value] : commonRS.m_MeshDatas)
        {
            const auto& meshData = commonRS.m_MeshDatas[key];
            const auto& fixedMesh = meshData->m_FixedMesh;

            if (fixedMesh)
            {
                fxcc::graph::common::DataIndex matDataIndex = key;

                m_Effect->SetFixed();
                m_Effect->SetObjData(transform);
                fixedMesh->Bind();
                fixedMesh->DrawElementsAuto();

            }
        }
        m_Effect->End();
        m_RenderTarget->End();

        m_DepthRT->Begin();
        m_DepthEffect->Begin();
        m_DepthEffect->ClearBgColor(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
        m_DepthEffect->ClearBuffer(1, 1);
        m_DepthEffect->SetCamera(lightCamera);

        for (auto [key, value] : commonRS.m_MeshDatas)
        {
            const auto& fixedMesh = value->m_FixedMesh;
            if (fixedMesh)
            {
                m_DepthEffect->SetFixed();
                fixedMesh->Bind();
                fixedMesh->DrawElementsAuto();
            }
        }
        m_DepthEffect->End();
        m_DepthRT->End();
    };

    void OnFramebuffersize(int w, int h) override
    {
        opengl3::GlfwApp::OnFramebuffersize(w, h);
        camera.SetAspect(m_Desc.Aspect());

        fxcc::graph::common::RenderTargetJack::Desc desc(1, w, h, 4, 0);
        m_RenderTarget = std::make_shared<fxcc::graph::opengl3::RenderTargetJack>(desc);

      
    }
    void OnUpdate() override
    {
        opengl3::GlfwApp::OnUpdate();

        float ctTime = m_GlfwlTimer.m_CurrentTime;
        camera.FreeMove(m_Input, m_GlfwlTimer.m_DeltaTime, { 50.0f,50.0f });

        float deltaTime = m_GlfwlTimer.m_DeltaTime;

        if (m_UpdateLight)
        {
            m_Theta += deltaTime;
         
        }
        glm::vec3 lightPos = glm::vec3(glm::sin(m_Theta) * 35.f + 35.f, glm::cos(m_Theta) * 35 + 35, 50.f);
        lightCamera.SetPosition(lightPos);
        lightCamera.LookAt(glm::vec3(0.0f));

        m_BoneNode->ProcessNode(fxcc::graph::common::Transform());
        m_BoneNode->UpdateBuffer(m_BoneData);
    }
};

int main()
{
    ztclog::pushFunction([](const ztclog::Line &line)
                         {
		if (line.m_Type != ztclog::Line::Type::_line_debug_)
		{
			std::cout << line.m_Text << std::endl;
		} });

    using _App = SimpleApp;
    using _Desc = fxcc::platform::common::App::Desc;

    _Desc desc;
    desc.m_Pos = glm::ivec2(100, 100);
    desc.m_Size = glm::ivec2(900, 600);
    desc.m_ScaleFactor = 1.0f;

    desc.m_Title = "test model";
    std::shared_ptr<_App> _app = std::make_shared<_App>(desc);
    return _app->Run();
};
